/* 
 * A664p7_support.c -- Implements general support functions.
 *
 *
 *------------------------------------------------------------------------------
 *
 *                    ---   PROPRIETARY NOTICE   ---
 *
 *   The information contained herein is proprietary to Smiths Aerospace.
 *   and shall not be reproduced or disclosed in whole or in part or used
 *   for any design or manufacture except when such user possesses direct
 *   written authorization from Smiths Aerospace.
 *
 *   Copyright 2006, Smiths Aerospace.
 *
 *------------------------------------------------------------------------------
 *
 * modification history
 * --------------------
 * 30 mar 06	Huizenga	Initial revision.
 * 10 mar 06    Huizenga	Added get_A664p7_devices, A664p7_auto_tx_mode, A664p7_auto_rx_mode
 */

#include "pcap.h"

// Disable deprecated warning for sprintf and scanf
#pragma warning( disable : 4996 )

#ifndef WIN32
	#include <sys/socket.h>
	#include <netinet/in.h>
#else
	#include <winsock.h>
#endif
#include "A664p7_internal.h"


int get_A664p7_devices(pcap_t **net_a_dev, pcap_t **net_b_dev, int create_console)
{
    int hCrt;
    FILE *hf;
	char errbuf[PCAP_ERRBUF_SIZE+1];
	pcap_if_t *alldevs;
	pcap_if_t *sel_dev, *sel_dev_2;
	int dev_count, selected_device_number;

	/* Redirect stdio */
	if(create_console)
	{
		AllocConsole();
		hCrt = _open_osfhandle((long) GetStdHandle(STD_OUTPUT_HANDLE), 0);
		hf = _fdopen( hCrt, "w" );
		*stdout = *hf;
		setvbuf( stdout, 0, _IONBF, 0 );
		hCrt = _open_osfhandle((long)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT);
		hf = _fdopen( hCrt, "r" );
		*stdin = *hf;
		setvbuf( stdin, NULL, _IONBF, 0 );
	}

	/* Retrieve the device list */
	if(pcap_findalldevs(&alldevs, errbuf) == -1)
	{
		fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
		return A664P7_FAIL;
	}
	

	printf("The following devices were found on your system:\n\n");
	dev_count = show_A664p7_device_list(alldevs);
	
	printf("\n\n*********************************************************\n");
	printf("Smiths Emulated A664P7 on Ethernet Driver Setup\n");
	printf("*********************************************************\n\n");
	printf("Review the list of network adapters above, and select\n");
	printf("the adapters to use for \"Network A\" and \"Network B.\"\n");
	printf("The selected adapters should be connected to the\n");
	printf("corresponding A664P7 port on your GPM or SBC. The IP addresses\n");
	printf("of your network adapters have been printed above for\n");
	printf("your reference, to help you identify them.  These IP\n");
	printf("addresses will have no effect upon A664P7 emulation mode:\n");
	printf("they are overridden by the A664P7 emulator to allow the\n");
	printf("generation of properly formed A664P7 packets -- you don't\n");
	printf("have to change any of your network card's settings.\n");
	printf("*********************************************************\n\n");



/*  Select and open the device for network A */
	selected_device_number = get_int("Enter the number of the device to use for Network A: ", 0, dev_count-1);
	sel_dev = get_device_by_number(alldevs, selected_device_number);

	printf("Opening device %i (%s)...\n", selected_device_number, sel_dev->name);
	if(!(*net_a_dev = open_A664p7_device(sel_dev->name)))
	{
	        pcap_freealldevs(alldevs);
			return A664P7_FAIL;
	}

/* Select and open the device for network B (if using two adapters) */
	if(dev_count > 1)
		selected_device_number = get_int("Enter the number of the device to use for Network B (-1 for no net B): ", -1, dev_count-1);
	else
		selected_device_number = -1;

	if(selected_device_number >= 0)
	{
		sel_dev_2 = get_device_by_number(alldevs, selected_device_number);
		printf("Opening device %i (%s)...\n", selected_device_number, sel_dev->name);
		if(!(*net_b_dev = open_A664p7_device(sel_dev_2->name)))
		{
				pcap_freealldevs(alldevs);
				return A664P7_FAIL;
		}
	}
	else
	{
		*net_b_dev = NULL;
	}
	return A664P7_SUCC;
}

/* A664p7_auto_tx_mode: Generates TX mode flags to match available network
 *                    devices
 *  INPUTS:  net_a_dev -- A pointer to the network A dev, or NULL if Network A
 *                        is not used.
 *           net_b_dev -- A pointer to the network B dev, or NULL if Network B
 *                        is not used.
 *  RETURNS: An tx_mode value that can be used with create_vl
 */
int A664p7_auto_tx_mode(pcap_t *net_a_dev, pcap_t *net_b_dev)
{
	int mode = 0;
	if(net_a_dev)
		mode |= VL_TX_A;
	if(net_b_dev)
		mode |= VL_TX_B;
	return mode;
}

/* A664p7_auto_rx_mode: Generates RX mode flags to match available network
 *                    devices
 *  INPUTS:  net_a_dev -- A pointer to the network A dev, or NULL if Network A
 *                        is not used.
 *           net_b_dev -- A pointer to the network B dev, or NULL if Network B
 *                        is not used.
 *  RETURNS: An rx_mode value that can be used with create_vl
 */
int A664p7_auto_rx_mode(pcap_t *net_a_dev, pcap_t *net_b_dev)
{
	int mode = 0;
	if(net_a_dev)
		mode |= VL_RX_A;
	if(net_b_dev)
		mode |= VL_RX_B;
	return mode;
}

pcap_if_t *get_device_by_number(pcap_if_t *device_list, int d_num)
{
	int i;
	pcap_if_t *device_enumerator = device_list;
	/* Pick off the selected device */
	for(device_enumerator=device_list, i = 0;device_enumerator && i < d_num;device_enumerator=device_enumerator->next, i++);
	return device_enumerator;
}

/* print relevant information on interfaces that are available on the system*/
int show_A664p7_device_list(pcap_if_t *d)
{
  pcap_addr_t *a;
  char iptos_buf[17];
  int i;

	/* Loop through the device list.
		NOTE: Here we throw away this function's copy of the 
			  pointer to the base of the device
			  list (d); it isn't needed later */
	for(i = 0;d; d=d->next, i++)
	{
		  printf("Interface %i:\n", i);
		  /* Name */
		  printf("\t%s\n",d->name);

		  /* Description */
		  if (d->description)
			printf("\tDescription: %s\n",d->description);

		  /* IP addresses */
		  for(a=d->addresses;a;a=a->next) {
			switch(a->addr->sa_family)
			{
			  case AF_INET:
				printf("\tAddress Family Name: AF_INET\n");
				if (a->addr)
				  printf("\tAddress: %s\n",iptos( ((struct sockaddr_in *)a->addr)->sin_addr.s_addr, iptos_buf));
				break;

			  case AF_INET6:
			   printf("\tUNUSABLE: IPV6 address detected.  An adapter used for A664P7 Transmission must have an IPV4 address.\n");
				break;

			  default:
				printf("\tUNUSABLE: Address Family Unknown. An adapter used for A664P7 Transmission must have an IPV4 address.\n");
				break;
			}
		  }
		  printf("\n");
	}
	return i;
}

pcap_t *open_A664p7_device(char *dev_name)
{ 
	pcap_t *device = NULL;
	char errbuf[PCAP_ERRBUF_SIZE+1];

	if(!(device = pcap_open(dev_name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 10, NULL, errbuf)))
	{
		printf("\n Unable to open the selected adapter. \n");
		return NULL;
	}

	/* A664P7 works on Ethernet links only, so we check here that we're on an Ethernet network. */
    if(pcap_datalink(device) != DLT_EN10MB)	/* DLT_EN10MB also covers Fast Ethernet .. the 10MB is to distinguish from EN3MB, which has different headers */
    {
		pcap_close(device);
        printf("A664P7 Transmission is supported on Ethernet adapters only.\n");
        return NULL;
    }

	return device;
}

void mem_die(void *pointer)
{
	if(!pointer)
	{
		printf("Memory error: Unexpected NULL pointer detected! Exiting...\n");
		exit(EXIT_FAILURE);
	}
}

/* Implements a filter for UDP in the kernel-level driver.  Not used at this time */
void enable_A664p7_filter(pcap_if_t *sel_dev, pcap_t *device)
{
    bpf_u_int32 netmask;
    char packet_filter[] = "udp";
    struct bpf_program fcode;

	if(sel_dev->addresses)
        /* Retrieve the mask of the first address of the interface */
        netmask=((struct sockaddr_in *)(sel_dev->addresses->netmask))->sin_addr.S_un.S_addr;
    else
        netmask=0xffffff; 

    //compile the filter
    if(pcap_compile(device, &fcode, packet_filter, 1, netmask) <0 )
	{
        fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
        return;
    }
    
    //set the filter
    if(pcap_setfilter(device, &fcode)<0)
	{
        fprintf(stderr,"\nError setting the filter.\n");
        return;
    }
}

void show_mac_address(unsigned char addr[6])
{
	int i, tmp;
	for(i = 0; i < sizeof(MAC_ADDRESS)-1; i++)
	{
		tmp = (int)addr[i];
		printf("%02hX:", tmp);
	}
	tmp = (int)addr[i];
	printf("%02hX", tmp);
}

void show_eth_header(PACKET *p)
{
	printf("DST: "); 
	show_mac_address(p->eh->dest);
	printf(" / SRC: ");
	show_mac_address(p->eh->source);
	printf("\n");
}

/* Convert an IP address to a string */
/* Parameters:
		in: unsigned long int containing the IP address to be converted
		out: pointer to a buffer long enough to hold an IP address (16 bytes) */
char *iptos(u_long in, char *out)
{
	u_char *p;
	p = (u_char *)&in;
	sprintf(out, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
	return out;
}

/* Gets an integer from the user, between lower bound "rangel" and upper bound "rangeh" */
int get_int(char *prompt, int rangel, int rangeh)
{
	int scanf_ret = 0;
	int return_value = rangel-1;
	while( ((return_value < rangel || return_value > rangeh) && !(rangel==0 && rangeh==0)) || scanf_ret < 1)
	{
		printf("%s", prompt);
		scanf_ret = scanf("%i", &return_value);
		
		/* If a range has been specified and not met OR user has entered a string / non-numeric */
		if( ((return_value < rangel || return_value > rangeh) && !(rangel==0 && rangeh==0)) || scanf_ret < 1)
		{
			printf("Invalid entry.  Valid range is %i to %i.\n", rangel, rangeh);
			
			/* Throw away strings ("six thousand twenty two") */
			if(scanf_ret < 1)
				scanf("%*s");
		}
	}
	return return_value;
}
